package client;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Scanner;

import repository.Repository;

public class MonitorImpl extends UnicastRemoteObject implements Monitor,
		Runnable, Serializable {

	private static final long serialVersionUID = -4186883508852614527L;

	public ArrayList<String> addresses;
	public ArrayList<Monitor> musicians;
	public String localhostName;
	public Repository musicrepo;
	public Player player;
	public Conductor conductor;
	public Thread currentThread;
	public String conductorAddress;
	public boolean isPerformingElection;
	public boolean answerMessage;
	public int songMusicians;

	public MonitorImpl() throws RemoteException {
		addresses = new ArrayList<String>();
		musicians = new ArrayList<Monitor>();
		songMusicians = 0;
	}

	public void run() {
		readMusicians("musicians.txt");
		conductorAddress = addresses.get(addresses.size() - 1);
		System.out.println(conductorAddress);

		for (String address : addresses) {
			try {
				Monitor m = (Monitor) Naming.lookup("rmi://" + address
						+ "/client");
				musicians.add(m);
			} catch (MalformedURLException e) {
				System.err.println(e.getMessage());
			} catch (RemoteException e) {
				System.err.println(e.getMessage());
			} catch (NotBoundException e) {
				System.err.println(e.getMessage());
			}
		}

		try {
			bindToRepo("repository.txt");
		} catch (Exception e) {
			System.err.println("Repository not found! " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
		
		try {
			songMusicians = musicrepo.getNumInstruments();
		} catch (RemoteException e1) {
			System.err.println("Couldn't get the original number of instruments!");
			e1.printStackTrace();
		}

		// ///////////////////////////////////////////////
		// Hard version for test without bully
		/*
		 * try { InetAddress addr = InetAddress.getLocalHost(); String hostname
		 * = addr.getHostName(); System.out.println(hostname);
		 * 
		 * if (hostname.equals("bo618-02u")) { conductor = new
		 * Conductor(musicians, musicrepo, 0); Thread c = new Thread(conductor);
		 * c.start(); } else { player = new Player(musicrepo); Thread p = new
		 * Thread(player); p.start(); } } catch (UnknownHostException e) {
		 * e.printStackTrace(); }
		 */

		// Find the localhost name
		try {
			InetAddress addr = InetAddress.getLocalHost();
			localhostName = addr.getHostName();
			System.out.print("localhostName :");
			System.out.println(localhostName);
		} catch (UnknownHostException e) {
			System.err.println(e.getMessage());
		}

		// Simple version for the first time
		election();

		while (true) {
			try {
				currentThread.join();
			} catch (InterruptedException e) {
				System.err.println(e.getMessage());
			} finally {
				if (conductor == null){
					if (player.isPlaying()) {
						System.err.println("Emergency election!");
						emergencyElection();
					}
					else{
						System.out.println("Song is done!");
						break;
					}
				} else {
					System.out.println("I am the conductor and the song is done!");
					break;
				}
			}
		}

		// Perform elections to decide on Conductor/Instruments
		// Instantiate Conductors and Instruments
	}

	private void readMusicians(String filename) {

		// Musicians file is considered sorting by inverse order instruments
		// priority or more likely to became conductor

		try {
			Scanner s = new Scanner(new File(filename));
			while (s.hasNextLine()) {
				addresses.add(s.nextLine());
				System.out.println(addresses);
			}
		} catch (FileNotFoundException e) {
			System.out.println(e.getMessage());
			System.exit(-1);
		}
	}

	private void bindToRepo(String filename) throws Exception {
//		try {
			Scanner s = new Scanner(new File(filename));
			String addr = s.next();
			System.out.println(addr);
			musicrepo = (Repository) Naming.lookup("rmi://" + addr + "/repository");
//		} catch (FileNotFoundException e) {
//			System.out.println(e.getMessage());
//		} catch (MalformedURLException e) {
//			System.out.println(e.getMessage());
//		} catch (RemoteException e) {
//			System.out.println(e.getMessage());
//		} catch (NotBoundException e) {
//			System.out.println(e.getMessage());
//		}
	}

	public void doStuff(String s) throws RemoteException {
		System.out.println(s);

	}

	public void updateTime(int curBar) throws RemoteException {
		player.syncTime(curBar);
	}

	public void election() {

		// Remove the conductor from the list of musicians
		musicians.remove(addresses.indexOf(conductorAddress));
		//addresses.remove(addresses.indexOf(conductorAddress));

		// Perform a first false election, the last host on the file is the
		// conductor
		if (localhostName.equals(conductorAddress)) {
			conductor = new Conductor(musicians, musicrepo, 0);
			player = null;
			currentThread = new Thread(conductor);
			currentThread.start();
		} else {
			player = new Player(musicrepo, addresses.indexOf(localhostName) % songMusicians);
			conductor = null;
			currentThread = new Thread(player);
			currentThread.start();
		}

	}

	public void bullyItselfIntoConductor(String s) {

		for (int i = 0; i < addresses.size() - 1; i++) {
			try {
				musicians.get(i).coordinatorMessage(s);
			} catch (RemoteException e) {
				System.err.println(e.getMessage());
			}

		}
		
		player = null;
		conductor = new Conductor(musicians, musicrepo, 0);
		currentThread = new Thread(conductor);
		currentThread.start();
		// Kill player thread and start conductor instead
		// TODO
	}

	public void emergencyElection() {
		// Find new Conductor if the current Conductor has failed

		System.out.println("Performing emergency election");
		isPerformingElection = true;

		String bestCandidate = addresses.get(addresses.size() - 1);

		// If current musician is best candidate to be conductor
		if (localhostName.equals(bestCandidate)) {
			bullyItselfIntoConductor(bestCandidate);
		} else {

			// If not send an election message to process with higher identifier
			// TODO check reverse list or not
			int i = musicians.size() - 1;
			String musicianAddress = addresses.get(i);
			while (!localhostName.equals(musicianAddress)) {
				try {
					System.out.println("Sending message to: "+ musicianAddress);
					musicians.get(i).electionMessage(localhostName);

				} catch (RemoteException e) {
					System.err.println(e.getMessage());
				}
				i--;
				musicianAddress =  addresses.get(i);
			}

			// Wait for the message answer
			try {
				Thread.sleep(player.getPrevBarAvg());
			} catch (InterruptedException e) {
				System.err.println(e.getMessage());
			}

			// if message arrives, wait for a conductor message (isPerforming)
			// if not bully itself
			if (answerMessage) {
				try {
					Thread.sleep(player.getPrevBarAvg());
				} catch (InterruptedException e) {
					System.err.println(e.getMessage());
				}

				if (isPerformingElection) {
					answerMessage = false;
					emergencyElection();
				}
			} else {
				bullyItselfIntoConductor(localhostName);
			}
		}
	}

	public void coordinatorMessage(String s) {
		// Someone bullied himself into new coordinator
		conductorAddress = s;
		musicians.remove(addresses.indexOf(conductorAddress));
		addresses.remove(addresses.indexOf(conductorAddress));
		isPerformingElection = false;
		
		// Relaunch musician process if stopped
		conductor = null;
		player = new Player(musicrepo, addresses.indexOf(localhostName) % songMusicians);
		currentThread = new Thread(player);
		currentThread.start();
	}

	public void electionMessage(String s) {

		try {
			musicians.get(addresses.indexOf(s)).answerMessage();
		} catch (RemoteException e) {
			System.err.println(e.getMessage());
		}

		if (!isPerformingElection) {
			// answerMessage ?
			emergencyElection();
		}
	}

	public void answerMessage() {
		answerMessage = true;
	}
}
